<template>
    <div class="base-table">
        <header v-if="$slots['tableHeader']" class="base-table-header">
            <slot name="tableHeader"></slot>
        </header>
        <div class="base-table-body">
            <!--表格-->
            <el-table
                ref="elTableRef"
                v-loading="loading"
                :class="[hideBackground ? 'base-table-el-bg-hide' : '']"
                :data="data"
                :style="{ height: showPageInfo ? '' : '100%' }"
                class="base-table-body-el"
                v-bind="$attrs"
                @selection-change="onRowSelect"
            >
                <!--序号-->
                <el-table-column v-if="showIndex" :width="indexColumnWidth as string" align="center" label="序号">
                    <template #default="scopedData">
                        <template v-if="$slots['index']">
                            <slot name="index" v-bind="scopedData"></slot>
                        </template>
                        <span
                            v-else
                            v-text="DataFormat.dataPadStart(scopedData.$index + 1, String(pageSize).length)"
                        ></span>
                    </template>
                </el-table-column>
                <!--多选复选框-->
                <el-table-column v-if="showSelection" align="center" type="selection" width="55" />
                <template v-for="(columnConfig, colIndex) of columns" :key="colIndex">
                    <!--序号-->
                    <el-table-column
                        v-if="columnConfig.prop === 'index'"
                        :align="columnConfig.align || 'center'"
                        :label="columnConfig.label || '序号'"
                        :width="columnConfig.width || (indexColumnWidth as string)"
                        v-bind="{ ...columnConfig, width: undefined, label: undefined, align: undefined } as any"
                    >
                        <template #default="scopedData">
                            <template v-if="$slots['index']">
                                <slot name="index" v-bind="scopedData"></slot>
                            </template>
                            <span
                                v-else
                                v-text="
                                    columnConfig.formatter
                                        ? columnConfig.formatter(scopedData.$index + 1, ...[])
                                        : DataFormat.dataPadStart(scopedData.$index + 1, String(pageSize).length)
                                "
                            ></span>
                        </template>
                    </el-table-column>
                    <!--多选复选框-->
                    <el-table-column
                        v-else-if="columnConfig.prop === 'selection' && showSelection"
                        align="center"
                        type="selection"
                        width="55"
                    />
                    <!--操作列-->
                    <el-table-column
                        v-else-if="columnConfig.prop === OperationsPropName"
                        :formatter="
                            ((row: any, column: TableColumnCtx<any>, cellValue: any, index: number) =>
                                formatColumnDate(columnConfig, cellValue, row, column, index)) as any
                        "
                        align="center"
                        v-bind="columnConfig"
                    >
                        <!--操作列的内容-->
                        <template #default="scopedData">
                            <template v-if="$slots[OperationsPropName]">
                                <slot :name="OperationsPropName" v-bind="scopedData"></slot>
                            </template>
                            <div v-else class="flex gap-[8px]">
                                <!--遍历每一个操作按钮-->
                                <template v-for="(btnItem, btnIndex) of columnConfig.buttonList" :key="btnIndex">
                                    <!--普通操作按钮-->
                                    <el-button
                                        v-if="!btnItem.children"
                                        :class="[
                                            btnItem.class
                                                ? isString(btnItem.class)
                                                    ? btnItem.class
                                                    : (btnItem.class as Function)(scopedData)
                                                : btnItem.type === OperationButtonType.delete
                                                  ? 'border-[#FFD1CC] bg-[#FFF2F0] text-[#DF3F3F]'
                                                  : 'border-[#deebfe] bg-[#ecf5ff] text-[#285fbb]',
                                        ]"
                                        class="h-[28px] px-[12px] py-[4px]"
                                        style="margin-left: 0"
                                        v-bind:="btnItem?.getBindAttribute?.(scopedData)"
                                        @click="onOperationsButtonClick(btnItem, scopedData)"
                                    >
                                        <BaseIcon :name="btnItem.iconName" class="mr-[4px]" />
                                        <span>{{ btnItem.label }}</span>
                                    </el-button>
                                    <!--带下来菜单的操作按钮：一般为更多-->
                                    <template v-else>
                                        <el-dropdown placement="bottom" popper-class="p-[8px] base-table-more">
                                            <el-button
                                                :class="[
                                                    btnItem.class
                                                        ? isString(btnItem.class)
                                                            ? btnItem.class
                                                            : (btnItem.class as Function)(scopedData)
                                                        : btnItem.type === OperationButtonType.delete
                                                          ? 'border-[#FFD1CC] bg-[#FFF2F0] text-[#DF3F3F]'
                                                          : 'border-[#deebfe] bg-[#ecf5ff] text-[#285fbb]',
                                                ]"
                                                class="h-[28px] px-[12px] py-[4px]"
                                                style="margin-left: 0"
                                                v-bind:="btnItem?.getBindAttribute?.(scopedData)"
                                            >
                                                <BaseIcon :name="btnItem.iconName" class="mr-[4px]" />
                                                <span>{{ btnItem.label }}</span>
                                            </el-button>
                                            <template #dropdown>
                                                <el-dropdown-menu>
                                                    <template v-for="(item, index) of btnItem.children" :key="index">
                                                        <el-dropdown-item
                                                            v-if="
                                                                isFunction(item.visible)
                                                                    ? (item.visible as Function)(scopedData)
                                                                    : isBoolean(item.visible)
                                                                      ? item.visible
                                                                      : true
                                                            "
                                                            @click="onOperationsButtonClick(item, scopedData)"
                                                        >
                                                            <div
                                                                :class="[
                                                                    item.class
                                                                        ? isString(item.class)
                                                                            ? item.class
                                                                            : (item.class as Function)(scopedData)
                                                                        : item.type === OperationButtonType.delete
                                                                          ? 'text-[#DF3F3F]'
                                                                          : 'text-[#285fbb]',
                                                                ]"
                                                                class="flex h-[28px] items-center px-[12px] py-[4px]"
                                                            >
                                                                <BaseIcon
                                                                    v-if="item.iconName"
                                                                    :name="item.iconName"
                                                                    class="mr-[4px]"
                                                                />
                                                                <span>{{ item.label }}</span>
                                                            </div>
                                                        </el-dropdown-item>
                                                    </template>
                                                </el-dropdown-menu>
                                            </template>
                                        </el-dropdown>
                                    </template>
                                </template>
                            </div>
                        </template>

                        <template v-if="$slots[`${OperationsPropName}-header`]" #header="scopedData">
                            <slot :name="`${OperationsPropName}-header`" v-bind="scopedData"></slot>
                        </template>
                    </el-table-column>
                    <!--普通列-->
                    <el-table-column
                        v-else
                        :formatter="
                            ((row: any, column: TableColumnCtx<any>, cellValue: any, index: number) =>
                                formatColumnDate(columnConfig, cellValue, row, column, index)) as any
                        "
                        :header-align="columnConfig?.headerAlign || columnConfig?.align || (headerAlign as string)"
                        :show-overflow-tooltip="
                            isBoolean(columnConfig.showOverflowTooltip)
                                ? columnConfig.showOverflowTooltip
                                : showOverflowTooltip
                        "
                        v-bind="columnConfig"
                    >
                        <!--暴露列的插槽-->
                        <template v-if="$slots[columnConfig.prop]" #default="scopedData">
                            <slot :name="columnConfig.prop" v-bind="scopedData"></slot>
                        </template>
                        <template v-if="$slots[`${columnConfig.prop}-header`]" #header="scopedData">
                            <slot :name="`${columnConfig.prop}-header`" v-bind="scopedData"></slot>
                        </template>
                    </el-table-column>
                </template>
            </el-table>
            <!--分页：强制显示分页：showPageInfo，pageSize 只有一页不显示-->
            <div v-if="isShowPageInfo()" class="base-table-body-page-info">
                <div v-if="showTotal" class="base-table-page-info-total">
                    <span>共</span>
                    <span class="total-number" v-text="total"></span>
                    <span>条记录</span>
                </div>
                <el-pagination
                    :current-page="currentPage"
                    :page-size="pageSize"
                    :page-sizes="[10, 20, 30, 50, 100, 200]"
                    :total="total"
                    background
                    layout="prev, pager, next, sizes"
                    v-bind="pageAttribute"
                    @current-change="onCurrentPageChange"
                    @size-change="onPageSizeChange"
                />
            </div>
        </div>
    </div>
</template>

<script lang="ts" setup>
/**
 * @ClassName BaseTable
 * @Description 表格组件，如果是树形结构，设置rowKey="id"即可。
 * @Author xuyizhuo
 * @Date 2024/12/20 21:13
 * @example
 */
import { ref, watch } from 'vue'
import DataFormat from '@/utils/DataFormat'
import type { TableColumnCtx } from 'element-plus'
import { ElMessageBox, ElTable, ElTableColumn } from 'element-plus'
import { at, isBoolean, isFunction, isNumber, isString } from 'lodash-es'
import {
    DataType,
    OperationButtonType,
    OperationItem,
    OperationsPropName,
    TableColumn,
    VueProps,
} from './BaseTableProps'

const vueProps = withDefaults(defineProps<VueProps>(), {
    // 显示的列
    // columns: [],
    headerAlign: 'left', // 总条数
    showOverflowTooltip: true,
    // 是否显示序号
    showIndex: false,
    indexColumnWidth: 60, // 默认的序号列宽度
    // 是否显示序号
    showSelection: false,
    // 是否显示分页
    showPageInfo: true,
    // 显示背景颜色
    hideBackground: true,
    showTotal: true, // 是否总记录数
    pageAttribute: {} as any, // 分页属性
    currentPage: 1, // 当前页
    pageSize: 0, // 当前页的记录数
    total: 0, // 总条数，在defineProps这里声明了，属性就不会出现在$attrs中
})
// row-select-change：表格选中事件，相当于selectionChange
const emits = defineEmits(['page-change', 'update:currentPage', 'update:pageSize'])

const elTableRef = ref<InstanceType<typeof ElTable>>()

// 当前勾选的行
const selectedRows: { ids: (string | number)[]; rows: any[] } = { ids: [], rows: [] }

/** 选择框勾选事件 */
const onRowSelect = (selection: any[]) => {
    // console.log('selection', selection)
    const ids: (string | number)[] = []
    const rows: any[] = []
    for (let index = 0, len = selection.length; index < len; index++) {
        ids.push(at(selection[index], vueProps.dataId || 'id')[0])
        rows.push(selection[index])
    }
    selectedRows.ids = ids
    selectedRows.rows = rows
}

/**
 * 列表复选框选中状态
 * @param {any[]} rows 选择或取消选择的列
 * @param {boolean} selected 是否选中
 */
const toggleRowSelection = (rows?: any[], selected = true) => {
    if (rows) {
        rows.forEach((row) => {
            elTableRef.value!.toggleRowSelection(row, selected)
        })
    } else {
        elTableRef.value!.clearSelection()
    }
}

// 获取当前表格选择的行
const getSelectionRows = () => {
    return selectedRows
}

// 格式化列数据
function formatColumnDate(
    columnConfig: TableColumn,
    value: string,
    row: any,
    column: TableColumnCtx<any>,
    index: number,
): string {
    // 1、在列中配置了自定义格式化
    if (columnConfig.formatter) {
        return columnConfig.formatter(value, { columnConfig, row, column, index })
    }

    // 2、没有配置，根据指定的数据类型进行数据格式化
    switch (columnConfig.dataType) {
        case DataType.date:
            return DataFormat.timeFormat(value) // 按时间格式化
        default:
            return DataFormat.getDefaultValue(value) // 默认格式化
    }
}

// 监听表格数据更新：（1）滚动条回到顶部
watch(
    () => vueProps.data,
    () => {
        // 数据更新时，滚动条回到顶部
        elTableRef.value?.scrollBarRef?.setScrollTop(0)
        selectedRows.ids = []
        selectedRows.rows = []
    },
)

/** 是否显示分页信息，数字：0：自动、小于0：强制不显示，大于0：强制显示 */
const isShowPageInfo = () => {
    const visible = vueProps.showPageInfo
    if (isNumber(visible)) {
        // 等于0，根据pageSize自动判断
        if (visible === 0) {
            return vueProps.pageSize <= 1
        }
        // 不等于零，强制显示。小于0：强制不显示
        return visible > 0
    }
    return visible
}

// 页数发生改变
const onCurrentPageChange = (newValue: number) => {
    emits('update:currentPage', newValue)
}

// 页面大小发生改变
const onPageSizeChange = (newValue: number) => {
    emits('update:currentPage', 1)
    emits('update:pageSize', newValue)
}

watch([() => vueProps.currentPage, () => vueProps.pageSize], () => {
    const { currentPage, pageSize } = vueProps
    emits('page-change', currentPage, pageSize)
})

/**
 * 操作按钮被点击
 * @param {OperationItem} item // 操作项配置
 * @param scopedData 行数据
 */
const onOperationsButtonClick = (item: OperationItem, scopedData: any) => {
    if (!item?.click) {
        return
    }
    // 如果不是删除 或 没有配置消息，则直接执行点击事件
    if (item.type !== OperationButtonType.delete || (isBoolean(item.getMessage) && item.getMessage === false)) {
        item?.click?.(scopedData)
        return
    }
    let message = ''
    if (isFunction(item.getMessage)) {
        message = (item.getMessage as Function)?.(scopedData)
    } else if (isString(item.getMessage)) {
        message = at(scopedData.row, item.getMessage)[0]
    }
    message = message ? `您确定要删除 “${message}” 吗？` : '您确定要删除吗？'
    ElMessageBox.alert(message, '警告', {
        type: 'warning',
        autofocus: false,
        showCancelButton: true,
        confirmButtonText: '确认',
        cancelButtonText: '取消',
    }).then(() => {
        item?.click?.(scopedData)
    })
}

defineExpose({
    elTableRef,
    getSelectionRows, // 获取当前表格选择的行
    toggleRowSelection, // 切换选中状态
})
</script>

<style lang="scss" scoped>
.base-table {
    display: flex;
    flex-direction: column;
    gap: 16px;
    width: 100%;
    height: 100%;
    // 表格头
    .base-table-header {
        width: 100%;
    }

    // 表格主体内容
    .base-table-body {
        display: flex;
        flex: 1 1 0;
        flex-direction: column;
        width: 100%;
        height: 0;

        // 超长文本弹出框样式定制
        :deep(.el-popper.is-dark) {
            color: #ffffff;
        }

        // 数据表格
        .base-table-body-el {
            flex: 1 1 0;
            width: 100%;
            height: 0;
        }

        // 表格分页信息
        .base-table-body-page-info {
            display: flex;
            flex-wrap: wrap;
            justify-content: flex-end;
            align-items: center;
            gap: 12px;
            margin-top: 8px;
            width: 100%;

            .base-table-page-info-total {
                margin-right: 12px;
                color: #909399;
                font-weight: 500;
                font-size: 14px;

                .total-number {
                    margin: 0 4px;
                    color: #285fbb;
                }
            }
        }

        // 表格样式定制
        :deep(.el-table) {
            tr {
                height: 44px;
            }

            // 标题
            thead th {
                --el-table-header-bg-color: #f9fafc;
                --el-table-header-text-color: #30333a;
                color: var(--el-table-header-text-color);
                font-weight: 500;
            }

            // 隐藏背景：设置背景颜色为透明色
            &.base-table-el-bg-hide {
                --el-table-bg-color: rgba(255, 255, 255, 0); // .el-table使用
                --el-bg-color: rgba(255, 255, 255, 0); // tr使用
            }

            // 鼠标移入高亮色
            &.el-table--enable-row-hover .el-table__body {
                tr:hover > td.el-table__cell {
                    background-color: rgba(153, 153, 153, 0.1);
                }
            }
        }
    }
}
</style>

<style lang="scss">
// 更多按钮
.base-table-more {
    width: 152px;
    // 菜单列表
    .el-dropdown-menu {
        display: flex;
        flex-direction: column;
        gap: 4px;

        &.el-dropdown-menu {
            padding: 8px !important;
        }

        // 菜单项
        .el-dropdown-menu__item {
            justify-content: center;
            border-radius: 4px;
            padding: 4px;
            // background: #f5f7fa;
        }
    }
}
</style>
